fix(security): harden _blank navigation against reverse-tabnabbing#608
fix(security): harden _blank navigation against reverse-tabnabbing#608lorenzbaum wants to merge 1 commit intonetbirdio:mainfrom
_blank navigation against reverse-tabnabbing#608Conversation
📝 WalkthroughWalkthroughAdds or centralizes security attributes for external links: components now compute/apply Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens external navigation across the dashboard to prevent reverse-tabnabbing by ensuring _blank links and window.open(...) calls are using noopener/noreferrer protections consistently.
Changes:
- Added/normalized
rel="noopener noreferrer"on various_blanklinks (including native<a>andnext/linkusages). - Centralized safe
relbehavior for shared link-like components (InlineLink,DropdownMenuItem) whentarget="_blank". - Updated
_blankwindow.open(...)usage to includenoopener,noreferrerwindow features.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/modules/setup-netbird-modal/MacOSTab.tsx | Adds rel="noopener noreferrer" to external _blank links. |
| src/modules/setup-netbird-modal/IOSTab.tsx | Adds rel="noopener noreferrer" to App Store _blank link. |
| src/modules/setup-netbird-modal/DockerTab.tsx | Adds rel="noopener noreferrer" to Docker docs _blank link. |
| src/modules/setup-netbird-modal/AndroidTab.tsx | Adds rel="noopener noreferrer" to Google Play _blank link. |
| src/modules/onboarding/OnboardingEnd.tsx | Adds rel="noopener noreferrer" to YouTube _blank link. |
| src/components/ui/HelpAndSupportButton.tsx | Removes redundant rel props now handled by DropdownMenuItem. |
| src/components/SidebarItem.tsx | Secures window.open for _blank with noopener,noreferrer. |
| src/components/InlineLink.tsx | Ensures _blank links always include noopener noreferrer (while preserving existing rel tokens). |
| src/components/DropdownMenu.tsx | Ensures _blank dropdown menu links always include noopener noreferrer (while preserving existing rel tokens). |
| src/app/(dashboard)/control-center/page.tsx | Adds rel="noopener noreferrer" to a native <a target="_blank">. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/InlineLink.tsx (1)
38-46: Consider extractingsafeRelnormalization into a shared helper.This logic now exists in both
InlineLinkandDropdownMenuItem; centralizing it would reduce drift and keep future security updates consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/InlineLink.tsx` around lines 38 - 46, Extract the rel-normalization logic into a shared helper (e.g., getSafeRel or normalizeRel) that accepts (rel?: string, target?: string) and returns the normalized rel string; implement it to split tokens, filter empties, add "noopener" and "noreferrer" when target === "_blank", de-duplicate and join tokens, then replace the inline memo in the InlineLink component (safeRel) and the similar logic in DropdownMenuItem to call this new helper; export the helper from a common utilities module so both components import and use it, and keep the memoization in InlineLink (React.useMemo) but use the helper inside it to avoid behavior changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/components/InlineLink.tsx`:
- Around line 38-46: Extract the rel-normalization logic into a shared helper
(e.g., getSafeRel or normalizeRel) that accepts (rel?: string, target?: string)
and returns the normalized rel string; implement it to split tokens, filter
empties, add "noopener" and "noreferrer" when target === "_blank", de-duplicate
and join tokens, then replace the inline memo in the InlineLink component
(safeRel) and the similar logic in DropdownMenuItem to call this new helper;
export the helper from a common utilities module so both components import and
use it, and keep the memoization in InlineLink (React.useMemo) but use the
helper inside it to avoid behavior changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9d2161ea-0d5e-4362-b0f6-9bdf948f7244
📒 Files selected for processing (10)
src/app/(dashboard)/control-center/page.tsxsrc/components/DropdownMenu.tsxsrc/components/InlineLink.tsxsrc/components/SidebarItem.tsxsrc/components/ui/HelpAndSupportButton.tsxsrc/modules/onboarding/OnboardingEnd.tsxsrc/modules/setup-netbird-modal/AndroidTab.tsxsrc/modules/setup-netbird-modal/DockerTab.tsxsrc/modules/setup-netbird-modal/IOSTab.tsxsrc/modules/setup-netbird-modal/MacOSTab.tsx
💤 Files with no reviewable changes (1)
- src/components/ui/HelpAndSupportButton.tsx
…curity (reverse-tabnabbing)
3e1d068 to
b502e4d
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/DropdownMenu.tsx (1)
114-122: Consider centralizingsafeRelgeneration to avoid security-policy drift.This logic is now duplicated with
src/components/InlineLink.tsx:37-56. Extracting a small shared helper (e.g.,buildSafeRel(target, rel)) would keep_blankhardening consistent across components.♻️ Proposed refactor (shared helper + local usage)
+// e.g. src/utils/linkSecurity.ts +export function buildSafeRel(target?: string, rel?: string) { + if (target !== "_blank") return rel; + + const tokens = new Set((rel ?? "").split(/\s+/).filter(Boolean)); + tokens.add("noopener"); + tokens.add("noreferrer"); + + return Array.from(tokens).join(" "); +}- const safeRel = React.useMemo(() => { - if (target !== "_blank") return rel; - - const tokens = new Set((rel ?? "").split(/\s+/).filter(Boolean)); - tokens.add("noopener"); - tokens.add("noreferrer"); - - return Array.from(tokens).join(" "); - }, [target, rel]); + const safeRel = React.useMemo(() => buildSafeRel(target, rel), [target, rel]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/DropdownMenu.tsx` around lines 114 - 122, The safeRel construction in DropdownMenu (the React.useMemo that computes safeRel) is duplicated with InlineLink; extract a small shared helper function like buildSafeRel(target, rel) that centralizes adding "noopener" and "noreferrer" when target === "_blank" and returns the joined string, then replace the inline useMemo in DropdownMenu (and the logic in InlineLink) to call buildSafeRel(target, rel); update imports where needed and ensure both components use the same helper to avoid security-policy drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/components/DropdownMenu.tsx`:
- Around line 114-122: The safeRel construction in DropdownMenu (the
React.useMemo that computes safeRel) is duplicated with InlineLink; extract a
small shared helper function like buildSafeRel(target, rel) that centralizes
adding "noopener" and "noreferrer" when target === "_blank" and returns the
joined string, then replace the inline useMemo in DropdownMenu (and the logic in
InlineLink) to call buildSafeRel(target, rel); update imports where needed and
ensure both components use the same helper to avoid security-policy drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 36636fc1-4a46-4b77-bffe-ceef12c656d8
📒 Files selected for processing (10)
src/app/(dashboard)/control-center/page.tsxsrc/components/DropdownMenu.tsxsrc/components/InlineLink.tsxsrc/components/SidebarItem.tsxsrc/components/ui/HelpAndSupportButton.tsxsrc/modules/onboarding/OnboardingEnd.tsxsrc/modules/setup-netbird-modal/AndroidTab.tsxsrc/modules/setup-netbird-modal/DockerTab.tsxsrc/modules/setup-netbird-modal/IOSTab.tsxsrc/modules/setup-netbird-modal/MacOSTab.tsx
💤 Files with no reviewable changes (1)
- src/components/ui/HelpAndSupportButton.tsx
✅ Files skipped from review due to trivial changes (7)
- src/modules/setup-netbird-modal/AndroidTab.tsx
- src/modules/setup-netbird-modal/DockerTab.tsx
- src/modules/onboarding/OnboardingEnd.tsx
- src/app/(dashboard)/control-center/page.tsx
- src/modules/setup-netbird-modal/MacOSTab.tsx
- src/modules/setup-netbird-modal/IOSTab.tsx
- src/components/SidebarItem.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/InlineLink.tsx
This PR standardizes secure external navigation across the dashboard.
Changes
_blanklinks in shared components (e.g.InlineLink,DropdownMenuItem).rel="noopener noreferrer"for additional link usages and native<a>elements across the codebase._blankwindow.open(...)usages to include secure features (noopener,noreferrer)._blanknavigation.Why
Prevents reverse-tabnabbing (
window.openeraccess) and aligns with common security-lint expectations.Validation
_blankflowsIssue ticket number and link
None
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
Bug Fixes
Refactor